library(tidyverse)
library(ggthemes)
library(scales)
library(forcats)
library(jpeg)
filename <- 'data/faces.csv'
faces <- read_csv(filename)
Parsed with column specification:
cols(
  cat_id = col_character(),
  cat_name = col_character(),
  artist_id = col_character(),
  artist_name = col_character(),
  genres = col_character(),
  face_count = col_integer(),
  face_id = col_character(),
  face_pitch = col_double(),
  face_yaw = col_double(),
  face_roll = col_double(),
  face_gender = col_character(),
  face_glasses = col_character(),
  face_height = col_integer(),
  face_width = col_integer(),
  face_beard = col_double(),
  face_moustache = col_double(),
  face_sideburns = col_double(),
  face_smile = col_double()
)
with_faces <- faces %>% filter(face_count > 0)
faces <- faces %>% mutate(has_face = face_count > 0)
with_faces <- faces %>% filter(face_count > 0)
unique_artists <- faces %>% distinct(cat_id, artist_id, .keep_all = TRUE)

Artist Counts

unique_artists %>%
  ggplot(aes(x = fct_rev(fct_infreq(cat_name)))) +
  coord_flip() +
  geom_bar() +
  scale_y_continuous(labels = comma) +
  labs(title = "Artists Per Category", y = "") +
  theme_fivethirtyeight()

byCat <- unique_artists %>% group_by(cat_name) %>% mutate(cat_count = n()) %>%
  arrange(-cat_count) %>%
  group_by(cat_name, has_face) %>% summarise(count = n(), per = count / cat_count[1], cat_count = cat_count[1]) %>% arrange(cat_count)
byCat %>% filter(has_face == TRUE) %>%
  ggplot(aes(x = fct_inorder(cat_name), y = count )) + 
  coord_flip() +
  geom_bar(stat = "identity") +
  scale_y_continuous(labels = comma) +
  labs(title = "Count of Artists with Faces", y = "") +
  theme_fivethirtyeight()

byCat %>% 
  ggplot(aes(x = fct_inorder(cat_name), y = per, fill = has_face)) + 
  coord_flip() +
  scale_y_continuous(labels = percent) +
  geom_bar(stat = "identity") + 
  labs(x = "", y = "", title = "Percent Artists with Face by Category") +
  theme_fivethirtyeight()

Face Counts

faces %>%
  ggplot(aes(x = (face_count))) +
  geom_histogram(binwidth = 1.0) +
  scale_y_continuous(labels = comma) +
  scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Face Count", x = "Number of Faces in Artist Image")

unique_artists %>%
  ggplot(aes(x = (face_count))) +
  geom_histogram(binwidth = 1.0) +
  scale_y_continuous(labels = comma) +
  scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Face Count for Artists", x = "Number of Faces in Artist Image")

Lots of no-faces detected and solo artists.

Face Count by Genre

byCatFaceCount <- unique_artists %>% filter(has_face == TRUE) %>%
  group_by(cat_name) %>% summarise(mean_face_count = mean(face_count), median_face_count = median(face_count))
  
unique_artists %>% filter(has_face == TRUE) %>% #filter(cat_id == "metal") %>%
  ggplot(aes(x = face_count)) +
  #geom_histogram(binwidth = 1.0) +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]), binwidth = 1.0) + 
  scale_y_continuous(labels = percent) +
  scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Distribution of Face Count for Artists by Category", x = "Number of Faces in Artist Image") +
  theme_fivethirtyeight()

Cool!

So Country and Hip Hop artists almost always have solo images.

But you can’t have a metal, punk, or rock band without at least a few people.

Gender

with_faces %>%
  ggplot(aes(x = 1, fill = face_gender)) + 
  scale_y_continuous(labels = comma) +
  scale_x_continuous(labels = c()) +
  geom_bar() + 
  labs(x = "", y = "", title = "Count of Faces by Gender") +
  theme_fivethirtyeight()

with_faces %>%
  ggplot(aes(x = 1, fill = face_gender)) + 
  scale_y_continuous(labels = percent) +
  scale_x_continuous(labels = c()) +
  geom_bar(aes(y = (..count..)/sum(..count..)), position = "dodge") + 
  labs(x = "", y = "", title = "Percent of Faces by Gender") +
  theme_fivethirtyeight()

Lots of Dudes in this data!

Gender by Category

with_faces %>% #filter(face_gender == 'female') %>% 
  ggplot(aes(x = face_gender))  +
  geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  #geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]), binwidth = 1.0) + 
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender of Faces by Category", x = "") +
  theme_fivethirtyeight()

with_faces %>% #filter(face_gender == 'female') %>% 
  ggplot(aes(x = face_gender))  +
  #geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  scale_y_continuous(labels = percent) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender Percents of Faces by Category", x = "") +
  theme_fivethirtyeight()

Glasses

with_faces %>%
  ggplot(aes(x = face_glasses, fill = face_glasses)) + 
  scale_y_continuous(labels = percent) +
  geom_bar(aes(y = (..count..)/sum(..count..)), position = "dodge") + 
  labs(x = "", y = "", title = "Percentage of Glasses on Faces", fill = "Glasses Type") +
  theme_fivethirtyeight()

Swimming Goggles ?? That can’t be right.

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_glasses == 'SwimmingGoggles') %>% filter(face_count == 1) 
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Well, ok. Maybe not.

with_faces %>% filter(!(face_glasses == 'NoGlasses')) %>% filter(!(face_glasses == 'SwimmingGoggles')) %>%
  ggplot(aes(x = face_glasses, fill = face_glasses)) + 
  scale_y_continuous(labels = percent) +
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  facet_wrap(~ cat_name) + 
  labs(x = "", y = "", title = "Sun Glasses vs Reading Glasses by Category", fill = "Glasses Type") +
  theme_fivethirtyeight()

3D Face Pose

There are 3 values recorded for face pose: Yaw, Roll, and Pitch.

Most explainations of these features only explain it for Aircraft, but here is my understanding for human faces:

Unfortunately, Pitch isn’t yet computed - so we can only look

Lets look at Yaw and Roll:

Yaw

with_faces %>%
  ggplot(aes(x = face_yaw)) +
  geom_histogram(binwidth = 2) +
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Yaw Metric", x = "")

Mean:

mean(with_faces$face_yaw)
[1] -1.516755

Median:

median(with_faces$face_yaw)
[1] -1.9

So there is a slight trend of the nose pointing to the left … or at least that is what the algorithm is detecting. Could be a bias!

But really, as we see below, -1 or -2 is really not visibly noticable as a head turn.

Yaw Examples

Light Negative Yaw

Faces with yaw between -1 and -1.8

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw < -1) %>% filter(face_yaw > -1.8)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Medium Negative Yaw

Faces with Yaw between -10 and -15

med_neg_yaw <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw < -10) %>% filter(face_yaw > -15)
row <- med_neg_yaw[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Medium Positive Yaw

Faces with Yaw between 10 and 15

med_pos_yaw <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw > 10) %>% filter(face_yaw < 15)
row <- med_pos_yaw[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Positive Yaw

Faces with > 30 Yaw

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_yaw > 30)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Roll

Let’s get our Roll on.

with_faces %>%
  ggplot(aes(x = face_roll)) +
  geom_histogram(binwidth = 2) +
  scale_y_continuous(labels = comma) +
  #scale_x_continuous(breaks = seq(0, 15, 1)) +
  theme_fivethirtyeight() +
  labs(title = "Distribution of Roll Metric", x = "")

mean(with_faces$face_roll)
[1] -0.9685942
median(with_faces$face_roll)
[1] -0.9

Roll looks to be distributed pretty evenly - but there is a longer tail to the right. The summary stats indicate there is a slight drag to the left.

Lets look at some examples!

Roll Examples

Light Negative Roll

Faces with roll between -1 and -2

face_ind <- c(1,2,3)
filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll < -1) %>% filter(face_roll > -2)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Negative Roll

Faces with roll < -20

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll < -20)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Heavy Positive Roll

Faces with roll > 20

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_roll > 20)
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

Facial Hair

Look just at Beards for a second:

with_faces %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = face_beard)) + 
  geom_freqpoly(binwidth = 0.01) + 
  labs(title = "Male Beard Likelihood Values")

Interesting! So this isn’t really a continuous variable, instead its descritized.

To double check that, here are the unique values of face_beard:

sort(unique(with_faces$face_beard))
 [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
facial_hair <- with_faces %>% tidyr::gather(key = hair_type, value = percent, face_beard, face_moustache, face_sideburns)
facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  geom_bar(position = "dodge") + 
  scale_y_continuous(labels = comma) +
  labs(title = "Male Facial Hair Counts", fill = "Hair Type") + 
  theme_fivethirtyeight()

facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  #geom_bar(position = "dodge") + 
  geom_bar(aes(y=..count../sum(..count..)), position = "dodge") + 
  scale_y_continuous(labels = percent) +
  labs(title = "Male Facial Hair Percents", fill = "Hair Type") + 
  theme_fivethirtyeight()

facial_hair %>% filter(face_gender == 'male') %>%
  ggplot(aes(x = as.factor(hair_ratio), fill = hair_type)) + 
  #geom_bar(position = "dodge") + 
  geom_bar(aes(y=..count../sum(..count..)), position = "dodge") + 
  scale_y_continuous(labels = percent) +
  labs(title = "Male Facial Hair Percents", fill = "Hair Type") + 
  facet_wrap(~ hair_type) +
  theme_fivethirtyeight()

What about female identified faces?

facial_hair %>% filter(face_gender == 'female') %>%
  ggplot(aes(x = hair_ratio, fill = hair_type)) + 
  geom_histogram(binwidth = 0.01) +
  labs(title = "Women don't have Facial Hair in this dataset / algorithm")

Facial Hair by Category

facial_hair %>% filter(face_gender == 'male') %>% filter(hair_type == 'face_beard') %>%
  ggplot(aes(x = as.factor(hair_ratio) )) + 
  #geom_bar(position = "dodge") + 
  #geom_bar(aes(y=..count../sum(..count..))) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..])) + 
  scale_y_continuous(labels = percent) +
  labs(title = "Beards by Category") + 
  facet_wrap(~ cat_name) +
  theme_fivethirtyeight()

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_gender == 'male') %>% filter(face_beard == 1.0) 
row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)

filtered_faces <- with_faces %>% filter(face_count == 1) %>% filter(face_gender == 'male') %>% filter(face_beard == 1.0) 

row <- filtered_faces[face_ind,]
filename <- paste("data/imgs/", row$cat_id, "/", row$artist_id, ".jpg", sep="")
knitr::include_graphics(filename)
male_hair <- with_faces %>% filter(face_gender == 'male') %>% mutate(has_facial_hair = face_beard + face_moustache > 0.4)
male_hair %>%
  ggplot(aes(x = has_facial_hair, fill = has_facial_hair))  +
  #geom_bar() +
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 1.0) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  scale_y_continuous(labels = percent) +
  #scale_x_continuous(breaks = seq(1, 14, 2)) +
  facet_wrap(~ cat_name) + 
  labs(title = "Gender Percents of Faces by Category", x = "") +
  theme_fivethirtyeight()

Smiles

Most artists appear not to be smiling.

with_faces %>% 
  ggplot(aes(x = face_smile)) + 
  geom_histogram(binwidth = 0.1) +
  labs(title = "Smile Distribution")

Gender doesn’t seem to matter much:

with_faces %>% 
  ggplot(aes(x = face_smile, fill = face_gender)) + 
  geom_histogram(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]),binwidth = 0.1 ) + 
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 0.1, position = "dodge") +
  facet_wrap(~ face_gender) +
  scale_y_continuous(labels = percent) +
  labs(title = "Smile Distribution by Gender", y = "", x = "")

with_faces %>% mutate(is_smiling = face_smile > 0.25) %>%
  ggplot(aes(x = is_smiling, fill = is_smiling)) + 
  geom_bar(aes(y = (..count..)/tapply(..count..,..PANEL..,sum)[..PANEL..]) ) + 
  #geom_histogram(aes(y=..count../sum(..count..)), binwidth = 0.1, position = "dodge") +
  facet_wrap(~ face_gender) +
  scale_y_continuous(labels = percent) +
  labs(title = "Smile Distribution by Gender", y = "", x = "")

LS0tCnRpdGxlOiAiQmFuZCBHYXplIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGZvcmNhdHMpCmBgYAoKYGBge3J9CmxpYnJhcnkoanBlZykKbGlicmFyeShrbml0cikKYGBgCgoKYGBge3J9CmZpbGVuYW1lIDwtICdkYXRhL2ZhY2VzLmNzdicKZmFjZXMgPC0gcmVhZF9jc3YoZmlsZW5hbWUpCgpgYGAKCmBgYHtyfQpmYWNlcyA8LSBmYWNlcyAlPiUgbXV0YXRlKGhhc19mYWNlID0gZmFjZV9jb3VudCA+IDApCndpdGhfZmFjZXMgPC0gZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID4gMCkKYGBgCgpgYGB7cn0KdW5pcXVlX2FydGlzdHMgPC0gZmFjZXMgJT4lIGRpc3RpbmN0KGNhdF9pZCwgYXJ0aXN0X2lkLCAua2VlcF9hbGwgPSBUUlVFKQpgYGAKCgojIyBBcnRpc3QgQ291bnRzCgoKYGBge3J9CnVuaXF1ZV9hcnRpc3RzICU+JQogIGdncGxvdChhZXMoeCA9IGZjdF9yZXYoZmN0X2luZnJlcShjYXRfbmFtZSkpKSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV9iYXIoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJBcnRpc3RzIFBlciBDYXRlZ29yeSIsIHkgPSAiIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKYGBge3J9CmJ5Q2F0IDwtIHVuaXF1ZV9hcnRpc3RzICU+JSBncm91cF9ieShjYXRfbmFtZSkgJT4lIG11dGF0ZShjYXRfY291bnQgPSBuKCkpICU+JQogIGFycmFuZ2UoLWNhdF9jb3VudCkgJT4lCiAgZ3JvdXBfYnkoY2F0X25hbWUsIGhhc19mYWNlKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpLCBwZXIgPSBjb3VudCAvIGNhdF9jb3VudFsxXSwgY2F0X2NvdW50ID0gY2F0X2NvdW50WzFdKSAlPiUgYXJyYW5nZShjYXRfY291bnQpCmBgYAoKYGBge3J9CmJ5Q2F0ICU+JSBmaWx0ZXIoaGFzX2ZhY2UgPT0gVFJVRSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmN0X2lub3JkZXIoY2F0X25hbWUpLCB5ID0gY291bnQgKSkgKyAKICBjb29yZF9mbGlwKCkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgbGFicyh0aXRsZSA9ICJDb3VudCBvZiBBcnRpc3RzIHdpdGggRmFjZXMiLCB5ID0gIiIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQoKYGBgCgpgYGB7cn0KYnlDYXQgJT4lIAogIGdncGxvdChhZXMoeCA9IGZjdF9pbm9yZGVyKGNhdF9uYW1lKSwgeSA9IHBlciwgZmlsbCA9IGhhc19mYWNlKSkgKyAKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiUGVyY2VudCBBcnRpc3RzIHdpdGggRmFjZSBieSBDYXRlZ29yeSIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQoKYGBgCgoKIyMgRmFjZSBDb3VudHMKCmBgYHtyfQpmYWNlcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSAoZmFjZV9jb3VudCkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLjApICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE1LCAxKSkgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBGYWNlIENvdW50IiwgeCA9ICJOdW1iZXIgb2YgRmFjZXMgaW4gQXJ0aXN0IEltYWdlIikKCmBgYAoKYGBge3J9CnVuaXF1ZV9hcnRpc3RzICU+JQogIGdncGxvdChhZXMoeCA9IChmYWNlX2NvdW50KSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEuMCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTUsIDEpKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEZhY2UgQ291bnQgZm9yIEFydGlzdHMiLCB4ID0gIk51bWJlciBvZiBGYWNlcyBpbiBBcnRpc3QgSW1hZ2UiKQoKYGBgCgpMb3RzIG9mIG5vLWZhY2VzIGRldGVjdGVkIGFuZCBzb2xvIGFydGlzdHMuCgoKIyMjIEZhY2UgQ291bnQgYnkgR2VucmUKCmBgYHtyfQpieUNhdEZhY2VDb3VudCA8LSB1bmlxdWVfYXJ0aXN0cyAlPiUgZmlsdGVyKGhhc19mYWNlID09IFRSVUUpICU+JQogIGdyb3VwX2J5KGNhdF9uYW1lKSAlPiUgc3VtbWFyaXNlKG1lYW5fZmFjZV9jb3VudCA9IG1lYW4oZmFjZV9jb3VudCksIG1lZGlhbl9mYWNlX2NvdW50ID0gbWVkaWFuKGZhY2VfY291bnQpKQogIApgYGAKCmBgYHtyfQoKYGBgCgoKYGBge3J9CnVuaXF1ZV9hcnRpc3RzICU+JSBmaWx0ZXIoaGFzX2ZhY2UgPT0gVFJVRSkgJT4lICNmaWx0ZXIoY2F0X2lkID09ICJtZXRhbCIpICU+JQogIGdncGxvdChhZXMoeCA9IGZhY2VfY291bnQpKSArCiAgI2dlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMS4wKSArCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIGJpbndpZHRoID0gMS4wKSArIAogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSwgYmlud2lkdGggPSAxLjApICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDE0LCAyKSkgKwogIGZhY2V0X3dyYXAofiBjYXRfbmFtZSkgKyAKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBGYWNlIENvdW50IGZvciBBcnRpc3RzIGJ5IENhdGVnb3J5IiwgeCA9ICJOdW1iZXIgb2YgRmFjZXMgaW4gQXJ0aXN0IEltYWdlIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKQ29vbCEKClNvIENvdW50cnkgYW5kIEhpcCBIb3AgYXJ0aXN0cyBhbG1vc3QgYWx3YXlzIGhhdmUgc29sbyBpbWFnZXMuCgpCdXQgeW91IGNhbid0IGhhdmUgYSBtZXRhbCwgcHVuaywgb3Igcm9jayBiYW5kIHdpdGhvdXQgYXQgbGVhc3QgYSBmZXcgcGVvcGxlLiAKCgojIyBHZW5kZXIKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JQogIGdncGxvdChhZXMoeCA9IDEsIGZpbGwgPSBmYWNlX2dlbmRlcikpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGMoKSkgKwogIGdlb21fYmFyKCkgKyAKICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9ICJDb3VudCBvZiBGYWNlcyBieSBHZW5kZXIiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgoKYGBge3J9CndpdGhfZmFjZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gMSwgZmlsbCA9IGZhY2VfZ2VuZGVyKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBjKCkpICsKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIlBlcmNlbnQgb2YgRmFjZXMgYnkgR2VuZGVyIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKTG90cyBvZiBEdWRlcyBpbiB0aGlzIGRhdGEhCgojIyMgR2VuZGVyIGJ5IENhdGVnb3J5CgoKYGBge3J9CndpdGhfZmFjZXMgJT4lICNmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ2ZlbWFsZScpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX2dlbmRlcikpICArCiAgZ2VvbV9iYXIoKSArCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIGJpbndpZHRoID0gMS4wKSArIAogICNnZW9tX2hpc3RvZ3JhbShhZXMoeSA9ICguLmNvdW50Li4pL3RhcHBseSguLmNvdW50Li4sLi5QQU5FTC4uLHN1bSlbLi5QQU5FTC4uXSksIGJpbndpZHRoID0gMS4wKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICNzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDE0LCAyKSkgKwogIGZhY2V0X3dyYXAofiBjYXRfbmFtZSkgKyAKICBsYWJzKHRpdGxlID0gIkdlbmRlciBDb3VudHMgb2YgRmFjZXMgYnkgQ2F0ZWdvcnkiLCB4ID0gIiIpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSAjZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdmZW1hbGUnKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9nZW5kZXIpKSAgKwogICNnZW9tX2JhcigpICsKICAjZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgYmlud2lkdGggPSAxLjApICsgCiAgZ2VvbV9iYXIoYWVzKHkgPSAoLi5jb3VudC4uKS90YXBwbHkoLi5jb3VudC4uLC4uUEFORUwuLixzdW0pWy4uUEFORUwuLl0pICkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogICNzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDE0LCAyKSkgKwogIGZhY2V0X3dyYXAofiBjYXRfbmFtZSkgKyAKICBsYWJzKHRpdGxlID0gIkdlbmRlciBQZXJjZW50cyBvZiBGYWNlcyBieSBDYXRlZ29yeSIsIHggPSAiIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKCiMjIEdsYXNzZXMKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JQogIGdncGxvdChhZXMoeCA9IGZhY2VfZ2xhc3NlcywgZmlsbCA9IGZhY2VfZ2xhc3NlcykpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIlBlcmNlbnRhZ2Ugb2YgR2xhc3NlcyBvbiBGYWNlcyIsIGZpbGwgPSAiR2xhc3NlcyBUeXBlIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKU3dpbW1pbmcgR29nZ2xlcyA/PyBUaGF0IGNhbid0IGJlIHJpZ2h0LgoKYGBge3J9CmZhY2VfaW5kIDwtIGMoMSwyLDMpCmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2dsYXNzZXMgPT0gJ1N3aW1taW5nR29nZ2xlcycpICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAKcm93IDwtIGZpbHRlcmVkX2ZhY2VzW2ZhY2VfaW5kLF0KZmlsZW5hbWUgPC0gcGFzdGUoImRhdGEvaW1ncy8iLCByb3ckY2F0X2lkLCAiLyIsIHJvdyRhcnRpc3RfaWQsICIuanBnIiwgc2VwPSIiKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhmaWxlbmFtZSkKYGBgCgoKV2VsbCwgb2suIE1heWJlIG5vdC4KCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoIShmYWNlX2dsYXNzZXMgPT0gJ05vR2xhc3NlcycpKSAlPiUgZmlsdGVyKCEoZmFjZV9nbGFzc2VzID09ICdTd2ltbWluZ0dvZ2dsZXMnKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9nbGFzc2VzLCBmaWxsID0gZmFjZV9nbGFzc2VzKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSApICsgCiAgZmFjZXRfd3JhcCh+IGNhdF9uYW1lKSArIAogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIlN1biBHbGFzc2VzIHZzIFJlYWRpbmcgR2xhc3NlcyBieSBDYXRlZ29yeSIsIGZpbGwgPSAiR2xhc3NlcyBUeXBlIikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCmBgYAoKIyMgM0QgRmFjZSBQb3NlCgpUaGVyZSBhcmUgMyB2YWx1ZXMgcmVjb3JkZWQgZm9yIGZhY2UgcG9zZTogWWF3LCBSb2xsLCBhbmQgUGl0Y2guIAoKTW9zdCBleHBsYWluYXRpb25zIG9mIHRoZXNlIGZlYXR1cmVzIG9ubHkgZXhwbGFpbiBpdCBmb3IgW0FpcmNyYWZ0XShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PXBRMjROdG5hTGw4KSwgYnV0IGhlcmUgaXMgbXkgdW5kZXJzdGFuZGluZyBmb3IgaHVtYW4gZmFjZXM6CgoqIFlhdzogVGhlIGRpcmVjdGlvbiB5b3VyIG5vc2UgaXMgcG9pbnRpbmcsIHRvIHRoZSBsZWZ0IG9yIHJpZ2h0LgoqIFJvbGw6IENvY2tpbmcgeW91ciBoZWFkIHRvIHRoZSBsZWZ0IG9yIHJpZ2h0LgoqIFBpdGNoOiBMb29raW5nIHVwIG9yIGRvd24uCgpVbmZvcnR1bmF0ZWx5LCBQaXRjaCBpc24ndCB5ZXQgY29tcHV0ZWQgLSBzbyB3ZSBjYW4gb25seSBsb29rIAoKTGV0cyBsb29rIGF0IFlhdyBhbmQgUm9sbDoKCiMjIyBZYXcKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JQogIGdncGxvdChhZXMoeCA9IGZhY2VfeWF3KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICNzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE1LCAxKSkgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBZYXcgTWV0cmljIiwgeCA9ICIiKQpgYGAKCk1lYW46CgpgYGB7cn0KbWVhbih3aXRoX2ZhY2VzJGZhY2VfeWF3KQpgYGAKCk1lZGlhbjoKCmBgYHtyfQptZWRpYW4od2l0aF9mYWNlcyRmYWNlX3lhdykKYGBgCgpTbyB0aGVyZSBpcyBhIHNsaWdodCB0cmVuZCBvZiB0aGUgbm9zZSBwb2ludGluZyB0byB0aGUgbGVmdCAuLi4gb3IgYXQgbGVhc3QgdGhhdCBpcyB3aGF0IHRoZSBhbGdvcml0aG0gaXMgZGV0ZWN0aW5nLiBDb3VsZCBiZSBhIGJpYXMhCgpCdXQgcmVhbGx5LCBhcyB3ZSBzZWUgYmVsb3csIC0xIG9yIC0yIGlzIHJlYWxseSBub3QgdmlzaWJseSBub3RpY2FibGUgYXMgYSBoZWFkIHR1cm4uCgoKIyMjIFlhdyBFeGFtcGxlcwoKKipMaWdodCBOZWdhdGl2ZSBZYXcqKgoKRmFjZXMgd2l0aCB5YXcgYmV0d2VlbiAtMSBhbmQgLTEuOAoKYGBge3J9CmZhY2VfaW5kIDwtIGMoMSwyLDMpCmZpbHRlcmVkX2ZhY2VzIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV95YXcgPCAtMSkgJT4lIGZpbHRlcihmYWNlX3lhdyA+IC0xLjgpCnJvdyA8LSBmaWx0ZXJlZF9mYWNlc1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKKipNZWRpdW0gTmVnYXRpdmUgWWF3KioKCkZhY2VzIHdpdGggWWF3IGJldHdlZW4gLTEwIGFuZCAtMTUKCmBgYHtyfQptZWRfbmVnX3lhdyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2VfeWF3IDwgLTEwKSAlPiUgZmlsdGVyKGZhY2VfeWF3ID4gLTE1KQpyb3cgPC0gbWVkX25lZ195YXdbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCioqTWVkaXVtIFBvc2l0aXZlIFlhdyoqCgpGYWNlcyB3aXRoIFlhdyBiZXR3ZWVuIDEwIGFuZCAxNQoKYGBge3J9Cm1lZF9wb3NfeWF3IDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2NvdW50ID09IDEpICU+JSBmaWx0ZXIoZmFjZV95YXcgPiAxMCkgJT4lIGZpbHRlcihmYWNlX3lhdyA8IDE1KQpyb3cgPC0gbWVkX3Bvc195YXdbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCioqSGVhdnkgUG9zaXRpdmUgWWF3KioKCkZhY2VzIHdpdGggPiAzMCBZYXcKCmBgYHtyfQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2VfeWF3ID4gMzApCnJvdyA8LSBmaWx0ZXJlZF9mYWNlc1tmYWNlX2luZCxdCmZpbGVuYW1lIDwtIHBhc3RlKCJkYXRhL2ltZ3MvIiwgcm93JGNhdF9pZCwgIi8iLCByb3ckYXJ0aXN0X2lkLCAiLmpwZyIsIHNlcD0iIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoZmlsZW5hbWUpCmBgYAoKCiMjIyBSb2xsCgpMZXQncyBnZXQgb3VyIFJvbGwgb24uCgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX3JvbGwpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAyKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgI3NjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTUsIDEpKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIFJvbGwgTWV0cmljIiwgeCA9ICIiKQpgYGAKCgpgYGB7cn0KbWVhbih3aXRoX2ZhY2VzJGZhY2Vfcm9sbCkKYGBgCgpgYGB7cn0KbWVkaWFuKHdpdGhfZmFjZXMkZmFjZV9yb2xsKQpgYGAKClJvbGwgbG9va3MgdG8gYmUgZGlzdHJpYnV0ZWQgcHJldHR5IGV2ZW5seSAtIGJ1dCB0aGVyZSBpcyBhIGxvbmdlciB0YWlsIHRvIHRoZSByaWdodC4gVGhlIHN1bW1hcnkgc3RhdHMgaW5kaWNhdGUgdGhlcmUgaXMgYSBzbGlnaHQgZHJhZyB0byB0aGUgbGVmdC4gICAKCkxldHMgbG9vayBhdCBzb21lIGV4YW1wbGVzIQoKIyMjIFJvbGwgRXhhbXBsZXMKCioqTGlnaHQgTmVnYXRpdmUgUm9sbCoqCgpGYWNlcyB3aXRoIHJvbGwgYmV0d2VlbiAtMSBhbmQgLTIKCmBgYHtyfQpmYWNlX2luZCA8LSBjKDEsMiwzKQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2Vfcm9sbCA8IC0xKSAlPiUgZmlsdGVyKGZhY2Vfcm9sbCA+IC0yKQpyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCioqSGVhdnkgTmVnYXRpdmUgUm9sbCoqCgpGYWNlcyB3aXRoIHJvbGwgPCAtMjAKCmBgYHtyfQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2Vfcm9sbCA8IC0yMCkKcm93IDwtIGZpbHRlcmVkX2ZhY2VzW2ZhY2VfaW5kLF0KZmlsZW5hbWUgPC0gcGFzdGUoImRhdGEvaW1ncy8iLCByb3ckY2F0X2lkLCAiLyIsIHJvdyRhcnRpc3RfaWQsICIuanBnIiwgc2VwPSIiKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhmaWxlbmFtZSkKYGBgCgoqKkhlYXZ5IFBvc2l0aXZlIFJvbGwqKgoKRmFjZXMgd2l0aCByb2xsID4gMjAKCmBgYHtyfQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2Vfcm9sbCA+IDIwKQpyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCgojIyBGYWNpYWwgSGFpcgoKTG9vayBqdXN0IGF0IEJlYXJkcyBmb3IgYSBzZWNvbmQ6CgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9iZWFyZCkpICsgCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDAuMDEpICsgCiAgbGFicyh0aXRsZSA9ICJNYWxlIEJlYXJkIExpa2VsaWhvb2QgVmFsdWVzIikKYGBgCgpJbnRlcmVzdGluZyEgU28gdGhpcyBpc24ndCByZWFsbHkgYSBjb250aW51b3VzIHZhcmlhYmxlLCBpbnN0ZWFkIGl0cyBkZXNjcml0aXplZC4gCgpUbyBkb3VibGUgY2hlY2sgdGhhdCwgaGVyZSBhcmUgdGhlIHVuaXF1ZSB2YWx1ZXMgb2YgYGZhY2VfYmVhcmRgOgoKYGBge3J9CnNvcnQodW5pcXVlKHdpdGhfZmFjZXMkZmFjZV9iZWFyZCkpCmBgYAoKCgpgYGB7cn0KZmFjaWFsX2hhaXIgPC0gd2l0aF9mYWNlcyAlPiUgdGlkeXI6OmdhdGhlcihrZXkgPSBoYWlyX3R5cGUsIHZhbHVlID0gaGFpcl9yYXRpbywgZmFjZV9iZWFyZCwgZmFjZV9tb3VzdGFjaGUsIGZhY2Vfc2lkZWJ1cm5zKQpgYGAKCgpgYGB7cn0KZmFjaWFsX2hhaXIgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnbWFsZScpICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihoYWlyX3JhdGlvKSwgZmlsbCA9IGhhaXJfdHlwZSkpICsgCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGxhYnModGl0bGUgPSAiTWFsZSBGYWNpYWwgSGFpciBDb3VudHMiLCBmaWxsID0gIkhhaXIgVHlwZSIpICsgCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgpgYGB7cn0KZmFjaWFsX2hhaXIgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnbWFsZScpICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3RvcihoYWlyX3JhdGlvKSwgZmlsbCA9IGhhaXJfdHlwZSkpICsgCiAgI2dlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKyAKICBnZW9tX2JhcihhZXMoeT0uLmNvdW50Li4vc3VtKC4uY291bnQuLikpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpICsKICBsYWJzKHRpdGxlID0gIk1hbGUgRmFjaWFsIEhhaXIgUGVyY2VudHMiLCBmaWxsID0gIkhhaXIgVHlwZSIpICsgCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgpgYGB7cn0KCmZhY2lhbF9oYWlyICU+JSBmaWx0ZXIoZmFjZV9nZW5kZXIgPT0gJ21hbGUnKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoaGFpcl9yYXRpbyksIGZpbGwgPSBoYWlyX3R5cGUpKSArIAogICNnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsgCiAgZ2VvbV9iYXIoYWVzKHk9Li5jb3VudC4uL3N1bSguLmNvdW50Li4pKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgbGFicyh0aXRsZSA9ICJNYWxlIEZhY2lhbCBIYWlyIFBlcmNlbnRzIiwgZmlsbCA9ICJIYWlyIFR5cGUiKSArIAogIGZhY2V0X3dyYXAofiBoYWlyX3R5cGUpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCgpXaGF0IGFib3V0IGBmZW1hbGVgIGlkZW50aWZpZWQgZmFjZXM/CgpgYGB7cn0KZmFjaWFsX2hhaXIgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnZmVtYWxlJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaGFpcl9yYXRpbywgZmlsbCA9IGhhaXJfdHlwZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjAxKSArCiAgbGFicyh0aXRsZSA9ICJXb21lbiBkb24ndCBoYXZlIEZhY2lhbCBIYWlyIGluIHRoaXMgRGF0YXNldCAvIEFsZ29yaXRobSIpCmBgYAoKIyMjIEZhY2lhbCBIYWlyIGJ5IENhdGVnb3J5CgoKCgoKYGBge3J9CgpmYWNpYWxfaGFpciAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lIGZpbHRlcihoYWlyX3R5cGUgPT0gJ2ZhY2VfYmVhcmQnKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoaGFpcl9yYXRpbykgKSkgKyAKICAjZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArIAogICNnZW9tX2JhcihhZXMoeT0uLmNvdW50Li4vc3VtKC4uY291bnQuLikpKSArIAogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGxhYnModGl0bGUgPSAiQmVhcmRzIGJ5IENhdGVnb3J5IikgKyAKICBmYWNldF93cmFwKH4gY2F0X25hbWUpICsKICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQpgYGAKCmBgYHtyfQpmaWx0ZXJlZF9mYWNlcyA8LSB3aXRoX2ZhY2VzICU+JSBmaWx0ZXIoZmFjZV9jb3VudCA9PSAxKSAlPiUgZmlsdGVyKGZhY2VfZ2VuZGVyID09ICdtYWxlJykgJT4lIGZpbHRlcihmYWNlX2JlYXJkID09IDEuMCkgCgpyb3cgPC0gZmlsdGVyZWRfZmFjZXNbZmFjZV9pbmQsXQpmaWxlbmFtZSA8LSBwYXN0ZSgiZGF0YS9pbWdzLyIsIHJvdyRjYXRfaWQsICIvIiwgcm93JGFydGlzdF9pZCwgIi5qcGciLCBzZXA9IiIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGZpbGVuYW1lKQpgYGAKCgpgYGB7cn0KZmlsdGVyZWRfZmFjZXMgPC0gd2l0aF9mYWNlcyAlPiUgZmlsdGVyKGZhY2VfY291bnQgPT0gMSkgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnbWFsZScpICU+JSBmaWx0ZXIoZmFjZV9iZWFyZCA9PSAxLjApIAoKcm93IDwtIGZpbHRlcmVkX2ZhY2VzW2ZhY2VfaW5kLF0KZmlsZW5hbWUgPC0gcGFzdGUoImRhdGEvaW1ncy8iLCByb3ckY2F0X2lkLCAiLyIsIHJvdyRhcnRpc3RfaWQsICIuanBnIiwgc2VwPSIiKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhmaWxlbmFtZSkKYGBgCgpgYGB7cn0KbWFsZV9oYWlyIDwtIHdpdGhfZmFjZXMgJT4lIGZpbHRlcihmYWNlX2dlbmRlciA9PSAnbWFsZScpICU+JSBtdXRhdGUoaGFzX2ZhY2lhbF9oYWlyID0gZmFjZV9iZWFyZCArIGZhY2VfbW91c3RhY2hlID4gMC40KQpgYGAKCgpgYGB7cn0KbWFsZV9oYWlyICU+JQogIGdncGxvdChhZXMoeCA9IGhhc19mYWNpYWxfaGFpciwgZmlsbCA9IGhhc19mYWNpYWxfaGFpcikpICArCiAgI2dlb21fYmFyKCkgKwogICNnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmNvdW50Li4vc3VtKC4uY291bnQuLikpLCBiaW53aWR0aCA9IDEuMCkgKyAKICBnZW9tX2JhcihhZXMoeSA9ICguLmNvdW50Li4pL3RhcHBseSguLmNvdW50Li4sLi5QQU5FTC4uLHN1bSlbLi5QQU5FTC4uXSkgKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBwZXJjZW50KSArCiAgI3NjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgMTQsIDIpKSArCiAgZmFjZXRfd3JhcCh+IGNhdF9uYW1lKSArIAogIGxhYnModGl0bGUgPSAiR2VuZGVyIFBlcmNlbnRzIG9mIEZhY2VzIGJ5IENhdGVnb3J5IiwgeCA9ICIiKSArCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKYGBgCgojIyBTbWlsZXMKCk1vc3QgYXJ0aXN0cyBhcHBlYXIgbm90IHRvIGJlIHNtaWxpbmcuCgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZmFjZV9zbWlsZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEpICsKICBsYWJzKHRpdGxlID0gIlNtaWxlIERpc3RyaWJ1dGlvbiIpCmBgYAoKR2VuZGVyIGRvZXNuJ3Qgc2VlbSB0byBtYXR0ZXIgbXVjaDoKCmBgYHtyfQp3aXRoX2ZhY2VzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBmYWNlX3NtaWxlLCBmaWxsID0gZmFjZV9nZW5kZXIpKSArIAogIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSxiaW53aWR0aCA9IDAuMSApICsgCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIGJpbndpZHRoID0gMC4xLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBmYWNldF93cmFwKH4gZmFjZV9nZW5kZXIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGxhYnModGl0bGUgPSAiU21pbGUgRGlzdHJpYnV0aW9uIGJ5IEdlbmRlciIsIHkgPSAiIiwgeCA9ICIiKQpgYGAKCgoKCgpgYGB7cn0Kd2l0aF9mYWNlcyAlPiUgbXV0YXRlKGlzX3NtaWxpbmcgPSBmYWNlX3NtaWxlID4gMC4yNSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaXNfc21pbGluZywgZmlsbCA9IGlzX3NtaWxpbmcpKSArIAogIGdlb21fYmFyKGFlcyh5ID0gKC4uY291bnQuLikvdGFwcGx5KC4uY291bnQuLiwuLlBBTkVMLi4sc3VtKVsuLlBBTkVMLi5dKSApICsgCiAgI2dlb21faGlzdG9ncmFtKGFlcyh5PS4uY291bnQuLi9zdW0oLi5jb3VudC4uKSksIGJpbndpZHRoID0gMC4xLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBmYWNldF93cmFwKH4gZmFjZV9nZW5kZXIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gcGVyY2VudCkgKwogIGxhYnModGl0bGUgPSAiU21pbGUgRGlzdHJpYnV0aW9uIGJ5IEdlbmRlciIsIHkgPSAiIiwgeCA9ICIiKQpgYGAKCgo=